#include <userver/server/handlers/tests_control.hpp>

#include <userver/clients/http/component.hpp>
#include <userver/components/component.hpp>
#include <userver/server/http/http_error.hpp>
#include <userver/testsuite/testsuite_support.hpp>
#include <userver/yaml_config/merge_schemas.hpp>

#include <testsuite/impl/actions/caches.hpp>
#include <testsuite/impl/actions/control.hpp>
#include <testsuite/impl/actions/dynamic_config_defaults.hpp>
#include <testsuite/impl/actions/http_allowed_urls_extra.hpp>
#include <testsuite/impl/actions/logcapture.hpp>
#include <testsuite/impl/actions/logflush.hpp>
#include <testsuite/impl/actions/metrics_portability.hpp>
#include <testsuite/impl/actions/periodic.hpp>
#include <testsuite/impl/actions/reset_metrics.hpp>
#include <testsuite/impl/actions/tasks.hpp>

#ifndef ARCADIA_ROOT
#include "generated/src/server/handlers/tests_control.yaml.hpp"  // Y_IGNORE
#endif

USERVER_NAMESPACE_BEGIN

namespace server::handlers {

namespace {
const std::string kDefaultAction = "control";
}

TestsControl::TestsControl(
    const components::ComponentConfig& config,
    const components::ComponentContext& component_context
)
    : HttpHandlerJsonBase(config, component_context)
{
    auto& testsuite_support = component_context.FindComponent<components::TestsuiteSupport>();

    const bool skip_unregistered_testpoints = config["skip-unregistered-testpoints"].As<bool>(false);
    if (!skip_unregistered_testpoints) {
        testsuite_support.GetTestpointControl().SetAllEnabled();
    }

    const auto testpoint_url = config["testpoint-url"].As<std::optional<std::string>>();

    if (testpoint_url) {
        auto& http_client = component_context.FindComponent<components::HttpClient>().GetHttpClient();
        const auto testpoint_timeout = config["testpoint-timeout"].As<std::chrono::milliseconds>(std::chrono::seconds(1)
        );
        testpoint_client_ = std::make_unique<
            testsuite::impl::HttpTestpointClient>(http_client, *testpoint_url, testpoint_timeout);
        testsuite_support.GetTestpointControl().SetClient(*testpoint_client_);
    }

    namespace actions = testsuite::impl::actions;

    // Default tests/control action
    actions_
        .emplace(kDefaultAction, std::make_unique<actions::Control>(component_context, testpoint_client_ != nullptr));

    // Periodic tasks support
    actions_.emplace("run_periodic_task", std::make_unique<actions::PeriodicTaskRun>(testsuite_support));
    actions_.emplace("suspend_periodic_tasks", std::make_unique<actions::PeriodicTaskSuspend>(testsuite_support));

    // Cache support
    actions_.emplace("write_cache_dumps", std::make_unique<actions::CacheDumpsWrite>(testsuite_support));
    actions_.emplace("read_cache_dumps", std::make_unique<actions::CacheDumpsRead>(testsuite_support));

    // Metrics
    actions_.emplace("reset_metrics", std::make_unique<actions::ResetMetrics>(component_context));
    actions_.emplace("metrics_portability", std::make_unique<actions::MetricsPortability>(component_context));

    // Log capture
    actions_.emplace("log_capture", std::make_unique<actions::LogCapture>(component_context));

    // Log flush
    actions_.emplace("log_flush", std::make_unique<actions::LogFlush>(component_context));

    // Testsuite tasks support
    actions_.emplace("task_run", std::make_unique<actions::TaskRun>(testsuite_support));
    actions_.emplace("task_spawn", std::make_unique<actions::TaskSpawn>(testsuite_support));
    actions_.emplace("task_stop", std::make_unique<actions::TaskStop>(testsuite_support));
    actions_.emplace("tasks_list", std::make_unique<actions::TasksList>(testsuite_support));

    // HTTP client
    actions_.emplace("http_allowed_urls_extra", std::make_unique<actions::HttpAllowedUrlsExtra>(testsuite_support));

    // Dynamic config
    actions_
        .emplace("get_dynamic_config_defaults", std::make_unique<actions::DynamicConfigDefaults>(component_context));
}

TestsControl::~TestsControl() = default;

formats::json::Value
TestsControl::HandleRequestJsonThrow(const http::HttpRequest& request, const formats::json::Value& request_body, request::RequestContext&)
    const {
    if (request.GetMethod() != http::HttpMethod::kPost) {
        throw ClientError();
    }

    const auto& path_action = request.GetPathArg(0);
    if (!path_action.empty() && path_action != kDefaultAction) {
        return PerformAction(path_action, request_body);
    }

    // If the request object contains "action" field, then we only perform that
    // action, without normal configuration steps. The rest of the fields are
    // considered in the context of that action.
    if (request_body.HasMember("action")) {
        const auto action_name = request_body["action"].As<std::string>();
        return PerformAction(action_name, request_body);
    }

    return PerformAction(kDefaultAction, request_body);
}

formats::json::Value TestsControl::PerformAction(
    const std::string& action_name,
    const formats::json::Value& request_body
) const {
    const auto it = actions_.find(action_name);
    if (it != actions_.end()) {
        return it->second->Perform(request_body);
    }
    LOG_ERROR() << "unknown tests/control action " << action_name;
    throw ClientError();
}

yaml_config::Schema TestsControl::GetStaticConfigSchema() {
    return yaml_config::MergeSchemasFromResource<HttpHandlerJsonBase>("src/server/handlers/tests_control.yaml");
}

}  // namespace server::handlers

USERVER_NAMESPACE_END
